#include "stdafx.h"
#include "PNGFinder.h"
#include "PNGFinderEstimationAlgorithm.h"
#include "PNGFinderSimulationAlgorithm.h"

using namespace System;
using namespace System::Linq;

namespace SpikingNeuronLib
{
	//==============================================================================================================================
	// Protected Members
	//==============================================================================================================================

	int PNGFinder::FindPolychronousGroups(String^ tag, int anchorNeuron, int maxDelay, double weightLimit, int numExcitatoryNeurons, int totalNeurons, int verboseLevel)
	{
		int numPolychronousGroups = 0;  // for the current anchor

		List<int>^ weightyPreNeurons = gcnew List<int>();
		for each(Connection^ inputConnection in IncomingConnectionDictionary[anchorNeuron])
		{
			if (inputConnection->Weight > weightLimit)
			{
				weightyPreNeurons->Add(inputConnection->PreNeuron);
			}
		}

		int combinations = weightyPreNeurons->Count;   // number of neurons to combine
		if (combinations >= GroupSize)
		{
			CombinationGenerator^ combinationGenerator = gcnew CombinationGenerator(GroupSize, combinations);

			if (verboseLevel >= 2)
			{
				//Console.WriteLine("Anchor {0}: combining {1} presynaptic neurons", anchorNeuron, weightyPreNeurons.Count);
				Console::WriteLine("Anchor {0}: ", anchorNeuron);
			}

			int numSearches = 0;

			// loop until all combinations of size anchorCombinationGroupSize (typically triplets) have been explored
			bool testNextAnchorCombination = true;
			while (testNextAnchorCombination)
			{
				array<int>^ indexCombinations = combinationGenerator->Current;
				array<int>^ selectedNeurons = gcnew array<int>(indexCombinations->Length);
				for (int i = 0; i < indexCombinations->Length; i++)
				{
					selectedNeurons[i] = weightyPreNeurons[indexCombinations[i]];
				}

				//Console.WriteLine("Testing {0} {1} {2}", _anchorCombinations[0], _anchorCombinations[1], _anchorCombinations[2]);
				//Console.Write("Testing {0} {1} {2}...", selectedNeurons[0], selectedNeurons[1], selectedNeurons[2]);

				numPolychronousGroups = SearchCurrentTriplet(tag, anchorNeuron, selectedNeurons, maxDelay, numPolychronousGroups,
					GroupSize, weightLimit, _maxPNGLength, numExcitatoryNeurons, totalNeurons,
					ConnectionPairDictionary, GroupedConnectionDictionary, verboseLevel);

				++numSearches;
				testNextAnchorCombination = combinationGenerator->GetNextCombinations(testNextAnchorCombination);
			}

			//Console.WriteLine("Searched {0} combinations", numSearches);
		}

		return numPolychronousGroups;
	}

	int GetPostNeuron(Connection^ connection) { return connection->PostNeuron; }
	int GetPreNeuron(Connection^ connection) { return connection->PreNeuron; }

	// Get all incoming connections for each postsynaptic neuron
	Dictionary<int, List<Connection^>^>^ PNGFinder::GetIncomingConnections(List<Connection^>^ connections)
	{
		Func<Connection^, int>^ selector = gcnew Func<Connection^, int>(&GetPostNeuron);
		return GetConnectionsByNeuron(connections, selector);
	}

	// Get all outgoing connections for each presynaptic neuron
	Dictionary<int, List<Connection^>^>^ PNGFinder::GetOutgoingConnections(List<Connection^>^ connections)
	{
		Func<Connection^, int>^ selector = gcnew Func<Connection^, int>(&GetPreNeuron);
		return GetConnectionsByNeuron(connections, selector);
	}

	Dictionary<int, List<Connection^>^>^ PNGFinder::GetConnectionsByNeuron(List<Connection^>^ connections, Func<Connection^, int>^ selectNeuron)
	{
		Dictionary<int, List<Connection^>^>^ connectionsByNeuron = gcnew Dictionary<int, List<Connection^>^>(1000);

		for each(Connection^ connection in connections)
		{
			int neuronKey = selectNeuron(connection);   // returns either the pre or post neuron from the connection

			List<Connection^>^ connectionList;
			if (connectionsByNeuron->ContainsKey(neuronKey))
			{
				connectionList = connectionsByNeuron[neuronKey];
			}
			else
			{
				connectionList = gcnew List<Connection^>(200);
				connectionsByNeuron->Add(neuronKey, connectionList);
			}

			connectionList->Add(connection);
		}

		return connectionsByNeuron;
	}

	Dictionary<Tuple<int, int>^, Connection^>^ PNGFinder::CreateConnectionDictionary(List<Connection^>^ connections)
	{
		// create a dictionary of Connection objects keyed by pre and post neuron
		Dictionary<Tuple<int, int>^, Connection^>^ connectionDictionary = gcnew Dictionary<Tuple<int, int>^, Connection^>(connections->Count);
		for each(Connection^ connection in connections)
		{
			// MG assuming non-duplicate pre-post pairs for now
			// CrossbarNetwork::CreateFixedConnectionList() prevents duplicates but Connections can be assigned externally
			//if (connectionDictionary.ContainsKey(new Tuple<int, int>(connection.PreNeuron, connection.PostNeuron)))
			//    Console.WriteLine("{0} {1}", connection.PreNeuron, connection.PostNeuron);
			connectionDictionary->Add(gcnew Tuple<int, int>(connection->PreNeuron, connection->PostNeuron), connection);
		}

		return connectionDictionary;
	}

	// create a dictionary, keyed by presynaptic neuron that maps each key onto a set of outgoing connections grouped by the connection delay
	Dictionary<int, Dictionary<int, List<Connection^>^>^>^ PNGFinder::GenerateDelayGroupedConnectionsByPreNeuron(Dictionary<int, List<Connection^>^>^ outgoingConnectionDictionary)
	{
		// create a dictionary with a set of grouped connections for each presynaptic neuron key
		Dictionary<int, Dictionary<int, List<Connection^>^>^>^ groupedConnectionDictionary =
			gcnew Dictionary<int, Dictionary<int, List<Connection^>^>^>(outgoingConnectionDictionary->Count);
		for each(KeyValuePair<int, List<Connection^>^>^ neuronConnections in outgoingConnectionDictionary)
		{
			int preNeuron = neuronConnections->Key;
			Dictionary<int, List<Connection^>^>^ groupedConnections = gcnew Dictionary<int, List<Connection^>^>(20);

			for each(Connection^ connection in neuronConnections->Value)
			{
				List<Connection^>^ connectionList;

				int delay = connection->Delay;
				if (groupedConnections->ContainsKey(delay))
				{
					connectionList = groupedConnections[delay];
				}
				else
				{
					connectionList = gcnew List<Connection^>();
					groupedConnections->Add(delay, connectionList);
				}

				connectionList->Add(connection);
			}

			groupedConnectionDictionary->Add(preNeuron, groupedConnections);
		}

		return groupedConnectionDictionary;
	}

	FiringEvent CopyFiringEvent(FiringEvent e)
	{
		return FiringEvent(e.Time, e.NeuronIndex, EventLabel::Foreground);
	}

	LinkedEventPair^ CopyLinkedEventPairs(LinkedEventPair^ linkedEvents)
	{
		return gcnew LinkedEventPair(linkedEvents->PreEvent, linkedEvents->PostEvent, linkedEvents->ConnectionWeight, linkedEvents->ConnectionDelay, linkedEvents->Layer);
	}

	void PNGFinder::OnFoundPNG(String^ tag, int numPolychronousGroups, int anchorNeuron, int numFired, int maxLinkLevel, int lastFired, array<FiringEvent>^ groupEvents, List<LinkedEventPair^>^ linkedEvents)
	{
		// copy the events (removing empty slots)
		Func<FiringEvent, FiringEvent>^ firingEventCopier = gcnew Func<FiringEvent, FiringEvent>(&CopyFiringEvent);
		array<FiringEvent>^ pngEvents =
			Enumerable::ToArray<FiringEvent>(
				Enumerable::Select<FiringEvent, FiringEvent>(
					Enumerable::Take<FiringEvent>(groupEvents, numFired),
					firingEventCopier)
			);

		// copy the links
		Func<LinkedEventPair^, LinkedEventPair^>^ linkedEventCopier = gcnew Func<LinkedEventPair^, LinkedEventPair^>(&CopyLinkedEventPairs);
		array<LinkedEventPair^>^ pngLinks =
			Enumerable::ToArray<LinkedEventPair^>(
			Enumerable::Select<LinkedEventPair^, LinkedEventPair^>(linkedEvents, linkedEventCopier)
			);
		PNGDescriptor^ descriptor = gcnew PNGDescriptor(pngLinks);

		FoundPNG(tag, numPolychronousGroups, anchorNeuron, numFired, maxLinkLevel, groupEvents[numFired - 1].Time, pngEvents, descriptor);
	}

	//==============================================================================================================================
	// Public Members
	//==============================================================================================================================

	PNGFinder::PNGFinder(List<Connection^>^ connections, int groupSize)
	{
		GroupSize = groupSize;

		// use a dictionary of incoming connections for each postsynaptic neuron to find strong connections to each anchor neuron
		IncomingConnectionDictionary = GetIncomingConnections(connections);

		// create a dictionary of Connection objects keyed by pre and post neuron
		ConnectionPairDictionary = CreateConnectionDictionary(connections);

		// create a dictionary, keyed by presynaptic neuron that maps each key onto a set of outgoing connections grouped by the connection delay
		Dictionary<int, List<Connection^>^>^ outgoingConnectionDictionary = GetOutgoingConnections(connections);
		GroupedConnectionDictionary = GenerateDelayGroupedConnectionsByPreNeuron(outgoingConnectionDictionary);

		NumPostspikes = gcnew array<int>(_maxPNGLength);
		GroupEvents = gcnew array<FiringEvent>(_maxPNGLength);
		Links = gcnew List<LinkedEventPair^>(2 * groupSize * _maxPNGLength);
	}

	PNGFinder^ PNGFinder::Create(PNGSearchAlgorithm algorithm, NetworkBase^ network, int groupSize)
	{
		PNGFinder^ finder;
		if (algorithm == PNGSearchAlgorithm::Estimation)
			finder = gcnew PNGFinderEstimationAlgorithm(network->Connections, network->ConnectionsPerNeuron, network->InhibitoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);
		else
			finder = gcnew PNGFinderSimulationAlgorithm(network->Connections, network->ExcitatoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);

		return finder;
	}

	PNGFinder^ PNGFinder::Create(PNGSearchAlgorithm algorithm, NetworkBase^ network, int groupSize, Func<NetworkBase^, List<Connection^>^>^ connectionSelector)
	{
		List<Connection^>^ connections = connectionSelector(network);

		PNGFinder^ finder;
		if (algorithm == PNGSearchAlgorithm::Estimation)
			finder = gcnew PNGFinderEstimationAlgorithm(connections, network->ConnectionsPerNeuron, network->InhibitoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);
		else
			finder = gcnew PNGFinderSimulationAlgorithm(connections, network->ExcitatoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);

		return finder;
	}

	void PNGFinder::BindEvent(PNGFinderFoundPNGEventHandler^ handler)
	{
		this->FoundPNG += handler;
	}

	void PNGFinder::UnbindEvent(PNGFinderFoundPNGEventHandler^ handler)
	{
		this->FoundPNG -= handler;
	}

	int PNGFinder::SearchTriplet(String^ tag, PNGSearchAlgorithm algorithm, NetworkBase^ network, int anchorNeuron, array<int>^ selectedNeurons, double weightLimit, int groupSize, PNGFinderFoundPNGEventHandler^ handler, int verboseLevel)
	{
		PNGFinder^ finder;
		if (algorithm == PNGSearchAlgorithm::Estimation)
			finder = gcnew PNGFinderEstimationAlgorithm(network->Connections, network->ConnectionsPerNeuron, network->InhibitoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);
		else
			finder = gcnew PNGFinderSimulationAlgorithm(network->Connections, network->ExcitatoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);

		finder->BindEvent(handler);

		return finder->SearchCurrentTriplet(tag, anchorNeuron, selectedNeurons, network->MaximumAxonalDelay, 0,
			groupSize, weightLimit, _maxPNGLength, network->ExcitatoryNeurons, network->TotalNeurons,
			finder->ConnectionPairDictionary, finder->GroupedConnectionDictionary, verboseLevel);
	}

	int PNGFinder::FindPolychronousGroups(NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int verboseLevel)
	{
		return FindPolychronousGroups(network, fromNeuron, toNeuron, weightLimit, verboseLevel, nullptr);
	}

	int PNGFinder::FindPolychronousGroups(NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int verboseLevel, String^ tag)
	{
		// for each potential anchor neuron (excitatory neurons only)...
		int totalGroupsFound = 0;
		for (int anchorNeuron = fromNeuron; anchorNeuron < toNeuron; anchorNeuron++)
		{
			totalGroupsFound += FindPolychronousGroups(tag, anchorNeuron, network->MaximumAxonalDelay,
				weightLimit, network->ExcitatoryNeurons, network->TotalNeurons, verboseLevel);
		}

		return totalGroupsFound;
	}

	//==============================================================================================================================
	// Public Static Members
	//==============================================================================================================================

	// static
	int PNGFinder::FindPolychronousGroups(PNGSearchAlgorithm algorithm, NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int groupSize, int verboseLevel)
	{
		return FindPolychronousGroups(algorithm, network, fromNeuron, toNeuron, weightLimit, groupSize, verboseLevel, nullptr);
	}

	// static
	int PNGFinder::FindPolychronousGroups(PNGSearchAlgorithm algorithm, NetworkBase^ network, int fromNeuron, int toNeuron, double weightLimit, int groupSize, int verboseLevel, String^ tag)
	{
		PNGFinder^ finder;
		if (algorithm == PNGSearchAlgorithm::Estimation)
			finder = gcnew PNGFinderEstimationAlgorithm(network->Connections, network->ConnectionsPerNeuron, network->InhibitoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);
		else
			finder = gcnew PNGFinderSimulationAlgorithm(network->Connections, network->ExcitatoryNeurons, network->TotalNeurons, groupSize, _maxPNGLength);

		// for each potential anchor neuron (excitatory neurons only)...
		int totalGroupsFound = 0;
		for (int anchorNeuron = fromNeuron; anchorNeuron < toNeuron; anchorNeuron++)
		{
			totalGroupsFound += finder->FindPolychronousGroups(tag, anchorNeuron, network->MaximumAxonalDelay,
				weightLimit, network->ExcitatoryNeurons, network->TotalNeurons, verboseLevel);
		}

		return totalGroupsFound;
	}
}
